#include "layout.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>

void layout_init (Layout* layout, int width, int height, int layers, int traversals_size, int rooms_size)
{
	assert(layout != NULL);
	assert(width  >= 0);
	assert(height >= 0);
	assert(layers >= 0);
	assert(traversals_size >= 0);
	assert(rooms_size >= 0);
	
	layout->tiles_size = width * height * layers;
	layout->tile = malloc (layout->tiles_size * sizeof(TileLayout));
	layout->width = width;
	layout->height = height;
	layout->layers = layers;
	
	layout->traversals_size = traversals_size;
	layout->traversal = malloc (traversals_size * sizeof(Traversal));
	layout->n_traversals = 0;
	
	layout->rooms_size = rooms_size;
	layout->room = malloc (layout->rooms_size * sizeof(Room));
	layout->n_rooms = 0;
	
	memset (layout->tile, 0, layout->tiles_size * sizeof(TileLayout));
}

void layout_cleanup (Layout* layout)
{
	free (layout->tile);
	free (layout->traversal);
	free (layout->room);
	memset (layout, 0, sizeof(Layout));
}

void layout_extend (Layout* layout, int layers)
{
	assert(layers > 0);
	
	if (layout->layers < layers) {
		int capacity = layout->tiles_size / (layout->width * layout->height);
		
		if (layers > capacity) {
			while (layers > capacity) {
				if (capacity < 20) {
					capacity *= 2;
				} else {
					capacity += 5;
				}
			}
			
			int tiles_size = capacity * layout->width * layout->height;
			layout->tile = realloc (layout->tile, tiles_size * sizeof(TileLayout));
			
			TileLayout* begin = layout->tile + layout->tiles_size;
			int n_new_tiles = tiles_size - layout->tiles_size;
			memset (begin, 0, n_new_tiles * sizeof(TileLayout));
			
			layout->tiles_size = tiles_size;
		}
		
		layout->layers = layers;
	}
}

void layout_floor_rect (Layout* layout, Rect* rect)
{
	assert((rect->left   >= 0) && (rect->left   < layout->width));
	assert((rect->right  >= 0) && (rect->right  < layout->width));
	assert((rect->top    >= 0) && (rect->top    < layout->height));
	assert((rect->bottom >= 0) && (rect->bottom < layout->height));
	assert((rect->z      >= 0) && (rect->z      < layout->layers));
	
	for (int y = rect->top; y < rect->bottom; y++) 
	for (int x = rect->left; x < rect->right; x++) {
		TILE(layout,x,y,rect->z).floor = true;
	}
}

void layout_add_traversal (Layout* layout, TraversalType type, Coord anchor[2])
{
	assert(layout != NULL);
	assert((anchor[0].x >= 0) && (anchor[0].x < layout->width));
	assert((anchor[0].y >= 0) && (anchor[0].y < layout->height));
	assert((anchor[0].z >= 0) && (anchor[0].z < layout->layers));
	assert((anchor[1].x >= 0) && (anchor[1].x < layout->width));
	assert((anchor[1].y >= 0) && (anchor[1].y < layout->height));
	assert((anchor[1].z >= 0) && (anchor[1].z < layout->layers));
	assert(layout->n_traversals < layout->traversals_size);
	
	Traversal traversal = { type, { anchor[0], anchor[1] } };
	layout->traversal[layout->n_traversals++] = traversal;
}

int layout_add_room (Layout* layout, Rect* area)
{
	assert(layout != NULL);
	assert(area != NULL);
	assert((area->left   >= 0) && (area->left   < layout->width));
	assert((area->right  > 0) && (area->right  <= layout->width));
	assert((area->top    >= 0) && (area->top    < layout->height));
	assert((area->bottom > 0) && (area->bottom <= layout->height));
	assert((area->z      >= 0) && (area->z      < layout->layers));
	assert(layout->n_rooms < layout->rooms_size);
	
	layout->room[layout->n_rooms] = (Room) { *area, {}, 0, {}, 0 };
	
	for (int y = area->top; y < area->bottom; y++) 
	for (int x = area->left; x < area->right; x++) {
		TILE(layout,x,y,area->z).room = true;
	}
	
	return layout->n_rooms++;
}

void layout_add_room_entry (Layout* layout, int room, Coord entry)
{
	assert(layout != NULL);
	assert((room >= 0) && (room < layout->n_rooms));
	
	Room* r = &layout->room[room];
	
	assert(r->n_entries < MAX_ROOTS);
	assert((entry.x == r->area.left) || (entry.x == r->area.right-1) ||
	       (entry.y == r->area.top) || (entry.y == r->area.bottom-1));
	assert(entry.z == r->area.z);
	
	r->entry[r->n_entries++] = entry;
}

/* inserts an exit into a room */
void layout_add_room_exit (Layout* layout, int room, Coord exit)
{
	assert(layout != NULL);
	assert((room >= 0) && (room < layout->n_rooms));
	
	Room* r = &layout->room[room];
	
	assert(r->n_exits < MAX_BRANCHES);
	assert((exit.x == r->area.left) || (exit.x == r->area.right-1) ||
	       (exit.y == r->area.top) || (exit.y == r->area.bottom-1));
	assert(exit.z == r->area.z);
	
	r->exit[r->n_exits++] = exit;
}

#ifdef TEST_LAYOUT
#include <stdio.h>

void print_layout (Layout* layout, int tiles, int traversals, int rooms)
{
	if (tiles) {
		printf ("Tile Layout *************************\n");
		for (int z = 0; z < layout->layers; z++) {
			printf ("-- Layer %d --\n", z);
			for (int y = 0; y < layout->height; y++) {				
				for (int x = 0; x < layout->width; x++) {
					char c = ' ';
					int wall = !TILE(layout,x,y,z).floor;
					int room = TILE(layout,x,y,z).room;
					if (wall) c = 'X';
					if (wall && room) c = '=';
					printf ("%c ", c);
				}
				printf ("\n");
			}
			printf ("\n");
		}
	}
	
	if (traversals) {
		printf ("Traversals **************************\n");
		for (int i = 0; i < layout->n_traversals; i++) {
			Traversal* t = &layout->traversal[i];
			printf ("%s from (%d,%d,%d) to (%d,%d,%d)\n",
			        t->type == TRAVERSAL_STAIRS ? "Stairs" : "Teleporter",
			        t->anchor[0].x, t->anchor[0].y, t->anchor[0].z,
			        t->anchor[1].x, t->anchor[1].y, t->anchor[1].z);
		}
	}
	
	if (rooms) {
		printf ("Rooms *******************************\n");
		for (int i = 0; i < layout->n_rooms; i++) {
			Rect* r = &layout->room[i];
			printf ("On layer %d from (%d,%d) to (%d,%d)\n",
			        r->z, r->left, r->top, r->right, r->bottom);
		}
	}
}

void print_layout_specs (Layout* layout)
{
	printf ("Layout is %dx%dx%d tiles, %d/%d rooms, %d/%d traversals.\n",
	        layout->width, layout->height, layout->layers, 
	        layout->n_rooms, layout->rooms_size,
	        layout->n_traversals, layout->traversals_size);
}

int main ()
{
	Layout layout;
	layout_init (&layout, 10, 10, 2, 50, 50);
	
	Rect floor = { 1, 1, 7, 6, 0 };
	layout_floor_rect (&layout, &floor);
	floor = (Rect) { 7, 3, 9, 4, 0 };
	layout_floor_rect (&layout, &floor);
	floor = (Rect) { 8, 3, 9, 8, 0 };
	layout_floor_rect (&layout, &floor);
	floor = (Rect) { 7, 7, 9, 9, 0 };
	layout_floor_rect (&layout, &floor);
	
	floor = (Rect) { 2, 2, 8, 8, 1 };
	layout_floor_rect (&layout, &floor);
	
	floor = (Rect) { 0, 0, 8, 7, 0 };
	layout_add_room (&layout, &floor);
	floor = (Rect) { 1, 1, 9, 9, 1 };
	layout_add_room (&layout, &floor);
	
	print_layout (&layout, 1, 1, 1);
	
	print_layout_specs (&layout);
	printf ("Resize layout...\n");
	layout_extend (&layout, 4);
	print_layout_specs (&layout);
	floor = (Rect) { 2, 2, 8, 8, 3 };
	layout_floor_rect (&layout, &floor);
	floor = (Rect) { 1, 1, 9, 9, 3 };
	layout_add_room (&layout, &floor);
	print_layout (&layout, 1, 0, 0);
	
	layout_cleanup (&layout);
	return 0;
}
#endif
